#!/usr/bin/env bash
set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
source "$PLUGIN_CORE_AVAILABLE_PATH/config/functions"
source "$PLUGIN_CORE_AVAILABLE_PATH/certs/functions"
source "$PLUGIN_CORE_AVAILABLE_PATH/domains/functions"
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config"

letsencrypt_update () {
  #shellcheck disable=SC2034
  declare desc="update the docker image used for ACME validation"
  dokku_log_info1 "Updating letsencrypt docker image..."
  docker pull "${PLUGIN_IMAGE}:${PLUGIN_IMAGE_VERSION}"
  dokku_log_verbose "Done updating"
}

letsencrypt_create_root () {
  #shellcheck disable=SC2034
  declare desc="Ensure the let's encrypt root directory exists"
  local app="$1"; verify_app_name "$app"

  local app_root="$DOKKU_ROOT/$app"
  local le_root="$app_root/letsencrypt"

  mkdir -p "$le_root"
}

letsencrypt_format_timediff() {
  #shellcheck disable=SC2034
  declare desc="format a time difference in seconds into a human-readable string"
  local td="$1"
  local negative_td=0

  if [ "$td" -lt 0 ]; then
    negative_td=1
    td=$(( - td ));
  fi

  local days=$(( td / (24 * 60 * 60) ));
  td=$((td % (24 * 60 * 60) ));

  local hours=$(( td / (60 * 60)));
  td=$((td % (60 * 60)));

  local minutes=$(( td / 60 ));
  local secs=$(( td % 60 ));

  local res=""
  if [ $days -gt 0 ]; then
    res="${days}d, "
  fi

  if [ $hours -gt 0 ]; then
    res="${res}${hours}h, "
  fi

  if [ $minutes -gt 0 ]; then
    res="${res}${minutes}m, "
  fi

  if [ $secs -gt 0 ]; then
    res="${res}${secs}s, "
  fi

  # remove trailing comma
  res="$( echo "$res" | sed -re 's/, ?$//g')";

  if [[ $negative_td == 1 ]]; then
    res="${res} ago"
  fi

  echo "$res"
}

letsencrypt_get_expirydate() {
  #shellcheck disable=SC2034
  declare desc="print SSL certificate expiry date as UNIX timestamp"
  local app="$1"

  date -u -d "$(openssl x509 -in "$DOKKU_ROOT/$app/tls/server.crt" -enddate -noout | sed -e "s/^notAfter=//")" "+%s"
}

letsencrypt_is_active() {
  #shellcheck disable=SC2034
  declare desc="checks if app is secured by let's encrypt"
  local app=$1; verify_app_name "$app"

  # check if SSL is enabled on per-app level
  is_ssl_enabled "$app" || return 1

  # check if certificate is identical to the current let's encrypt certificate by comparing SHA1 hashes 
  local cert_sha1=$( (cat "$DOKKU_ROOT/$app/tls/server.crt" 2>/dev/null) | sha1sum || echo "not_found");
  local le_sha1=$( (cat "$DOKKU_ROOT/$app/letsencrypt/certs/current/fullchain.pem" 2>/dev/null) | sha1sum || echo "not_found" );
  [[ "$cert_sha1" == "$le_sha1" ]] || return 2

  echo "$app"
}

letsencrypt_list_apps_and_expiry() {
  #shellcheck disable=SC2034
  declare desc="list all letsencrypt-secured apps together with their expiry date"

  # prints a tab-separated list of
  #  * app name
  #  * expiry dates as UNIX timestamp (seconds since epoch)
  #  * selected renewal grace period (in seconds)
  #  * time left on certificate (in seconds)
  #  * time until renewal (in seconds)

  for app in $(dokku_apps); do
    if [[ "$app" == "=====>" ]] || [[ "$app" == "My" ]] || [[ "$app" == "Apps" ]]; then continue; fi
    if [[ "$(letsencrypt_is_active "$app")" ]]; then
      local expiry=$(letsencrypt_get_expirydate "$app")
      local grace_period=$(config_get --global DOKKU_LETSENCRYPT_GRACEPERIOD || config_get "$app" DOKKU_LETSENCRYPT_GRACEPERIOD || echo $((60 * 60 * 24 * 30)) );
      local time_to_expiry=$(( expiry - $(date +%s) ))
      local time_to_renewal=$(( expiry - grace_period - $(date +%s) ))
      echo -e "$app\t$expiry\t$grace_period\t$time_to_expiry\t$time_to_renewal"
    fi
  done
}

letsencrypt_configure_and_get_dir() {
  #shellcheck disable=SC2034
  declare desc="assemble simp_le command line arguments and create a config hash directory for them"

  local app="$1"; verify_app_name "$app"

  local app_root="$DOKKU_ROOT/$app"
  local le_root="$app_root/letsencrypt"

  eval "$(config_export global)"
  eval "$(config_export app "$app")"

  # build up a string of all certificate-controlling configuration settings.
  # this will be used to determine the folder name for the account key and certificates

  # get the selected ACME server
  local server=${DOKKU_LETSENCRYPT_SERVER:-default}
  if [ -z "$server" ] ||  [ "$server" == "default" ]; then
    server="https://acme-v02.api.letsencrypt.org/directory"
  elif [ "$server" == "staging" ]; then
    server="https://acme-staging-v02.api.letsencrypt.org/directory"
  fi

  # construct domain arguments
  local domains="$(get_app_domains "$app")"
  local domain_args=''
  for domain in $domains; do
    dokku_log_verbose " - Domain '$domain'" >&2
    domain_args="$domain_args -d $domain"
  done

  local config="--server $server --email $DOKKU_LETSENCRYPT_EMAIL $domain_args"

  local config_hash=$(echo "$config" | sha1sum | awk '{print $1}')
  local config_dir="$le_root/certs/$config_hash"; mkdir -p "$config_dir"

  # store config settings
  echo "$config" > "$config_dir/config"

  echo "$config_dir"
}

letsencrypt_check_email() {
  #shellcheck disable=SC2034
  declare desc="Check if an e-mail address is provided globally or for the app"

  local app="$1"; verify_app_name "$app"

  eval "$(config_export global)"
  eval "$(config_export app "$app")"

  local email="$DOKKU_LETSENCRYPT_EMAIL"

  # check we have a valid e-mail address
  if [ -z "$email" ]; then
    dokku_log_warn "ERROR: Cannot request a certificate without an e-mail address!"
    dokku_log_warn "  please provide your e-mail address using"
    dokku_log_warn "  dokku config:set --no-restart $app DOKKU_LETSENCRYPT_EMAIL=<e-mail>"
    return 1
  fi
}


